#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <SDL/SDL.h>

#include "primitives.h"
#include "zombies.h"
#include "display.h"
#include "level.h"
#include "gfx.h"

unsigned char output[0x8000];
unsigned char rawset[2*8*4096];

SDL_Surface *tileset;
SDL_Surface *block;
SDL_Color bgpal[256];

void fillbackground()
{
	int x=0, y=0, i;
	unsigned char tile;
    SDL_Rect src,dst;
    
    src.w=src.h=64;
    dst.w=dst.h=64;

    // map format: upper 4 bits = Y
    //             lower 4 bits = X
    // multiply * 64 to match tileset position

	for(i=0;i<mapsize;i+=2)
	{
		tile=mapdata[i];
		src.x=(tile&0x0F)<<6;
		src.y=(tile&0xF0)<<2;
        dst.x=x*64;
        dst.y=y*64;
        
        SDL_BlitSurface(tileset, &src, background, &dst);
        if(st.showgrid)
            SDL_DrawRect(background,&dst,0xffffff);
        
		x++;
		if(x==level.width)
		{
			y++;
			x = 0;
		}
	}
}

void dumptileset()
{
    SDL_SaveBMP(tileset,"tileset.bmp");
    printf("Tileset dumped to file.\n");
}

void dumpbackground()
{
    SDL_SaveBMP(background,"background.bmp");
    printf("Background dumped to file.\n");
}

// draw select tile border
void drawtileborder()
{
    SDL_Rect dst;
    
    dst.w=dst.h=64;
    dst.x=(st.tx<<6)-bgoffset.x;
    dst.y=(st.ty<<6)-bgoffset.y;

    // apply directly to screen buffer
    SDL_DrawRect(screen,&dst,0xffff00);
}

// updates selected tile on background image
void updatetile(int tilecode)
{
    SDL_Rect src,dst;

    src.w=src.h=64;
    dst.w=dst.h=64;
    src.x=(mapdata[tilecode]&0x0F)<<6;
    src.y=(mapdata[tilecode]&0xF0)<<2;
    dst.x=st.tx<<6;
    dst.y=st.ty<<6;
    
    // replace with selected tile
    SDL_BlitSurface(tileset,&src,background,&dst);
}

void flip_surface(SDL_Surface *surface, int direction)
{
    unsigned char src[64];
    unsigned char dst[64];
    int x,rx,y,ry;

    // copy data to buffer
    memcpy(&src,(unsigned char *)surface->pixels,64);

    for(x=0,rx=7; x<8; x++,rx--)
    {
        for(y=0,ry=7; y<8; y++, ry--)
        {
            switch(direction)
            {
                case 0:
                    dst[(ry*8)+x]=src[(y*8)+x];
                    break;
                case 1:
                    dst[(y*8)+rx]=src[(y*8)+x];
                    break;
                case 2:
                    dst[(ry*8)+rx]=src[(y*8)+x];
                    break;
            }
        }
    }

    memcpy((unsigned char *)surface->pixels,&dst,64);
}

// decompress routine, converted from ASM code to C
void decompress()
{
    unsigned int offset;
    unsigned char *input;
    unsigned short datasize;
    unsigned char scrap[0x1000];
    int byte,inbytes=0;
    int in=2,out=0;
    int inscrap=0xfee;
    int a,b,c;

    offset=ROMToFile(level.tileset1);
    input=(unsigned char *)(romdata+offset);

    datasize=(input[0]|(input[1]<<8))&0xffff;

    memset(&scrap,0,sizeof(scrap));
    memset(&output,0,sizeof(output));

    for(;;)
    {
        if(inbytes==0)
        {
            inbytes=8;
            if(datasize--!=0)
                byte=input[in++];
            else
                break;
        }
        inbytes--;

        a=byte;
        byte>>=1;

        if(a&1)
        {
            a=byte;
            if(datasize--!=0)
                byte=input[in++];
            else
                break;

            output[out++]=byte;
            scrap[inscrap++]=byte;
            inscrap&=0xfff; // overflow check
            byte=a;
        }
        else
        {
            // -- step 1 read --
            a=byte;
            if(datasize--!=0)
                byte=input[in++];
            else
                break;

            // -- step 2 read --
            b=byte;
            if(datasize--!=0)
                byte=input[in++];
            else
                break;

            // -- step 3 read/write --
            c=(byte&0x0f)+2;
            b=((byte<<4)&0xf00)|b;

            for(;c>=0;c--)
            {
                byte=scrap[b++];
                scrap[inscrap++]=byte;
                output[out++]=byte;
                inscrap&=0xfff; // overflow check
                b&=0xfff;
            }
            byte=a;
        }
    }
}

void buildtiles()
{
    unsigned short *tile;
    int i, j, y, pal_num;
    SDL_Color pal[256];
    SDL_Rect dst;
    
    // uncompress tile data
    decompress();
    
    tile=(unsigned short *)output;

    // a tile consists of 64 small 8*8 block tiles.
    // the final tileset, consists of 256 tiles, each 64*64 in size.
    for(i=0; i<256; i++)
    {
        dst.x=(i%16)*64;
        dst.y=(i/16)*64;

        for(j=i*64; j<(i*64+64); j++)
        {
            // change block palette
            pal_num=((tile[j]&0x1C00)>>10)*16;
            memcpy(&pal,&bgpal[pal_num],sizeof(SDL_Color)*16);
            SDL_SetColors(block, pal, 0, 16);

            // copy tile from raw blocks
            y=(tile[j]&0x1FF)*64;
            memcpy((unsigned char *)block->pixels,&(rawset[y]),64);

            //flip-x
            if(tile[j]&0x4000) flip_surface(block,1);
            //flip-y
            if(tile[j]&0x8000) flip_surface(block,0);
            
            // blit on tileset surface
            SDL_BlitSurface(block,NULL,tileset,&dst);

            dst.x+=8;
            if(dst.x%64==0)
            {
                dst.x=(i%16)*64;
                dst.y+=8;	
            }
        }
    }
}

void loadpalette()
{
    unsigned int offset;
	unsigned short *color;
	int x;

    offset=ROMToFile(level.bgpal);
    color=(unsigned short *)(romdata+offset); //check alignment!
    
	for(x=0; x<256; x++)
	{
        bgpal[x].r=(color[x]&0x1F)<<3;
        bgpal[x].g=((color[x]&0x3E0)>>5)<<3;
        bgpal[x].b=((color[x]&0x7C00)>>10)<<3;
	}
}

void loadtiles()
{
	unsigned char *romgraphics;
	unsigned char t[4];

    memset(&rawset,0,sizeof(rawset));
    romgraphics=romdata+ROMToFile(level.tileset3);
    
	int n=0,i,j;
	for(i=0; i<16384; i+=32)
	{
		for(j=i; j<i+16; j+=2)
		{
			t[0]=romgraphics[j];
			t[1]=romgraphics[j+1];
			t[2]=romgraphics[j+16];
			t[3]=romgraphics[j+17];
		
            // convert from planar data, to rgb data
            rawset[n++]=((t[3]& 128) | ((t[2] & 128) >> 1) | ((t[1] & 128) >> 2) | ((t[0] & 128) >> 3)) >> 4;
			rawset[n++]=(((t[3] & 64) << 1) | ((t[2] & 64)) | ((t[1] & 64) >> 1) | ((t[0] & 64) >> 2)) >> 4;
			rawset[n++]=(((t[3] & 32) << 2) | ((t[2] & 32) << 1) | ((t[1] & 32)) | ((t[0] & 32) >> 1)) >> 4;
			rawset[n++]=(((t[3] & 16) << 3) | ((t[2] & 16) << 2) | ((t[1] & 16) << 1) | ((t[0] & 16))) >> 4;
			rawset[n++]=((t[3] & 8) | ((t[2] & 8) >> 1) | ((t[1] & 8) >> 2) | ((t[0] & 8) >> 3));
			rawset[n++]=(((t[3] & 4) << 1) | ((t[2] & 4)) | ((t[1] & 4) >> 1) | ((t[0] & 4) >> 2));
			rawset[n++]=(((t[3] & 2) << 2) | ((t[2] & 2) << 1) | ((t[1] & 2)) | ((t[0] & 2) >> 1));
			rawset[n++]=(((t[3] & 1) << 3) | ((t[2] & 1) << 2) | ((t[1] & 1) << 1) | ((t[0] & 1)));
		}
	}
}

void init_graphics()
{
    // final rendered image
    background=SDL_CreateRGBSurface(SDL_SWSURFACE,level.width*64,level.height*64,32,0,0,0,0);
    
    tileset=SDL_CreateRGBSurface(SDL_SWSURFACE,1024,1024,32,0,0,0,0);
    block=SDL_CreateRGBSurface(SDL_SWSURFACE,8,8,8,0,0,0,0);
}

void loadgraphics()
{
    // init surfaces
    init_graphics();
    
    loadpalette();
    loadtiles();

    // check if surface should be locked
    // due to direct pixel manipulation
    if(SDL_MUSTLOCK(background)) SDL_LockSurface(background);
    if(SDL_MUSTLOCK(tileset)) SDL_LockSurface(tileset);
    if(SDL_MUSTLOCK(block)) SDL_LockSurface(block);

    buildtiles();
    fillbackground();
    
    // check if should unlock
    if(SDL_MUSTLOCK(background)) SDL_UnlockSurface(background);
    if(SDL_MUSTLOCK(tileset)) SDL_UnlockSurface(tileset);
    if(SDL_MUSTLOCK(block)) SDL_UnlockSurface(block);
}
